#include "GRender.h"
using namespace Eigen;
GRender* pRender = NULL;

GRender::GRender(int nWidth, int nHeight)
{
	m_nWidth = nWidth;
	m_nHeight = nHeight;
	m_pFrameBuffer = NULL;
	m_zBuffer.resize(m_nWidth * m_nHeight);

	m_screenHandle = NULL;
	m_screenDc = NULL;

	m_pVertexShader = (VertexShader*)(new GVertexShader());
	m_pFragmentShader = (FragmentShader*)(new GFragmentShader());
}

void GRender::Init()
{
	WNDCLASS wc = { CS_BYTEALIGNCLIENT, (WNDPROC)&GRender::ProcessEvent, 0, 0, 0,
		NULL, NULL, NULL, NULL, _T("GRenderClass") };

	BITMAPINFO bi = { { sizeof(BITMAPINFOHEADER), m_nWidth, -m_nHeight, 1, 32, BI_RGB,
		m_nWidth * m_nHeight * 4, 0, 0, 0, 0 } };

	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.hInstance = GetModuleHandle(NULL);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	if (!RegisterClass(&wc)) 
		return;

	m_screenHandle = CreateWindow(_T("GRenderClass"), _T("GRender"),
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		0, 0, 0, 0, NULL, NULL, wc.hInstance, NULL);

	if (m_screenHandle == NULL) 
		return;

	HDC hDC = GetDC(m_screenHandle);
	m_screenDc = CreateCompatibleDC(hDC);
	ReleaseDC(m_screenHandle, hDC);

	HBITMAP	screen_hb = CreateDIBSection(m_screenDc, &bi, DIB_RGB_COLORS, (void**)&m_pFrameBuffer, 0, 0);
	if (screen_hb == NULL)
		return;
	SelectObject(m_screenDc, screen_hb);

	RECT rect = { 0, 0, m_nWidth, m_nHeight };
	int wx, wy, sx, sy;
	AdjustWindowRect(&rect, GetWindowLong(m_screenHandle, GWL_STYLE), 0);
	wx = rect.right - rect.left;
	wy = rect.bottom - rect.top;
	sx = (GetSystemMetrics(SM_CXSCREEN) - wx) / 2;
	sy = (GetSystemMetrics(SM_CYSCREEN) - wy) / 2;
	if (sy < 0) sy = 0;
	SetWindowPos(m_screenHandle, NULL, sx, sy, wx, wy, (SWP_NOCOPYBITS | SWP_NOZORDER | SWP_SHOWWINDOW));
	SetForegroundWindow(m_screenHandle);

	// Init Gdiplus
	Gdiplus::GdiplusStartupInput gdiplusstartupinput;
	ULONG_PTR gdiplustoken;
	Gdiplus::GdiplusStartup(&gdiplustoken, &gdiplusstartupinput, NULL);

	ShowWindow(m_screenHandle, SW_NORMAL);
}

void GRender::DrawPixel(int nX, int nY, unsigned int color)
{
	m_pFrameBuffer[GetFrameBufferIdx(nX, nY)] = color;
}

void GRender::Active()
{
	MSG msg;

	while (1)
	{
		if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) break;
		if (!GetMessage(&msg, NULL, 0, 0)) break;
		DispatchMessage(&msg);
	}

	HDC hDC = GetDC(m_screenHandle);
	BitBlt(hDC, 0, 0, m_nWidth, m_nHeight, m_screenDc, 0, 0, SRCCOPY);
	ReleaseDC(m_screenHandle, hDC);
}

void GRender::Clear()
{
	memset(m_pFrameBuffer, 0, sizeof(unsigned int) * m_nHeight * m_nWidth);
	std::fill(m_zBuffer.begin(), m_zBuffer.end(), std::numeric_limits<float>::lowest());
}

void GRender::Draw(std::vector<GTriangle*>& lstTriangle)
{
	Clear();
	DefaultRender(lstTriangle);
}

static UI_CONTROL control;
LRESULT GRender::ProcessEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	int mouse_x = 0;
	int mouse_y = 0;
	switch (msg) 
	{
		case WM_CLOSE:break;
		case WM_KEYDOWN:
			pRender->OnKeyDown(wParam);
			break;
		case WM_KEYUP:break;
		case WM_MOUSEMOVE:
			if (control.bDrag)
			{
				mouse_x = (int)LOWORD(lParam);
				mouse_y = (int)HIWORD(lParam);
				int nDeltaX = mouse_x - control.nLastMousePosX;
				int nDeltaY = -(mouse_y - control.nLastMousePosY);
				control.nLastMousePosX = mouse_x;
				control.nLastMousePosY = mouse_y;
				pRender->OnMouseDrag(nDeltaX, nDeltaY);
			}
			break;
		case WM_LBUTTONDOWN:
			control.bDrag = true;
			control.nLastMousePosX = (int)LOWORD(lParam);
			control.nLastMousePosY = (int)HIWORD(lParam);
			break;
		case WM_LBUTTONUP:
			control.bDrag = false;
			break;
		default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

void GRender::DefaultRender(std::vector<GTriangle*>& lstTriangle)
{
	for (GTriangle* pTriangle : lstTriangle)
	{
		vertex_shader_payload vertex_payload[3];
		vertex_shader_output vertex_output[3];
		
		for (int i = 0; i < 3; ++i)
		{
			vertex_payload[i].camera = &m_Camera;
			vertex_payload[i].position = pTriangle->vertices[i];
			vertex_payload[i].texcoord0 = pTriangle->tex_coords[i];
			vertex_output[i] = m_pVertexShader->CalcOutput(vertex_payload[i]);
		}

		Eigen::Vector3f viewspace_z = {vertex_output[0].sv_position.w(), vertex_output[0].sv_position.w() , vertex_output[0].sv_position.w()};
		for (int i = 0; i < 3; ++i)
		{
			vertex_output[i].sv_position /= vertex_output[i].sv_position.w();
		}

		Eigen::Vector4f screen_position[3];
		for (int i = 0; i < 3; ++i)
		{
			screen_position[i] = GetViewPortMatrix() * vertex_output[i].sv_position;
		}

		Bounds2d triangleBounds = CaclBounds(screen_position, 3);
		for (int i = triangleBounds.nMinX; i < triangleBounds.nMaxX; ++i)
		{
			for (int j = triangleBounds.nMinY; j < triangleBounds.nMaxY; ++j)
			{
				if (IsInsideTriangle(i, j, screen_position))
				{
					Eigen::Vector3f barycentry = ComputeBarycentric2D(i, j, screen_position);
					float zt = 1 / (barycentry[0] / viewspace_z[0] + barycentry[1] / viewspace_z[1] + barycentry[2] / viewspace_z[2]);
					if (zt >= 0)
						continue;

					float zp = barycentry[0] * vertex_output[0].sv_position.z() / viewspace_z[0] + barycentry[1] * vertex_output[1].sv_position.z() / viewspace_z[1]
						+ barycentry[2] * vertex_output[2].sv_position.z() / viewspace_z[2];
					zp *= zt;

					// Z-Buffer
					int nZIndex = GetFrameBufferIdx(i, j);
					if (m_zBuffer[nZIndex] > zp)
						continue;

					m_zBuffer[nZIndex] = zp;
					Eigen::Vector4f interpolated_texcoord0 = Interpolate(barycentry[0], barycentry[1], barycentry[2], vertex_output[0].texcoord0 / viewspace_z[0],
						vertex_output[1].texcoord0 / viewspace_z[1], vertex_output[2].texcoord0 / viewspace_z[2], zt);

					fragment_shader_payload frag_payload;
					frag_payload.uv = interpolated_texcoord0;
					frag_payload.main_tex = pTriangle->pTexture;
					int pixel_color = m_pFragmentShader->CalcOutput(frag_payload);
					DrawPixel(i, j, pixel_color);
				}
			}
		}
	}
}

int GRender::GetFrameBufferIdx(int nX, int nY)
{
	return (m_nHeight - 1 - nY) * m_nWidth + nX;
}

bool GRender::IsInsideTriangle(int nPosX, int nPosY, const Vector4f* pVert)
{
	float fScreenPosX = nPosX + 0.5f;
	float fScreenPosY = nPosY + 0.5f;

	Vector3f v[3];
	for (int i = 0; i < 3; i++)
		v[i] = { pVert[i].x(), pVert[i].y(), 1.0 };
	Vector3f f0, f1, f2;
	f0 = v[1].cross(v[0]);
	f1 = v[2].cross(v[1]);
	f2 = v[0].cross(v[2]);
	Vector3f p(fScreenPosX, fScreenPosY, 1.);
	if ((p.dot(f0) * f0.dot(v[2]) > 0) && (p.dot(f1) * f1.dot(v[0]) > 0) && (p.dot(f2) * f2.dot(v[1]) > 0))
		return true;
	return false;
}

Eigen::Vector3f GRender::ComputeBarycentric2D(float x, float y, const Eigen::Vector4f* v)
{
	float c1 = (x * (v[1].y() - v[2].y()) + (v[2].x() - v[1].x()) * y + v[1].x() * v[2].y() - v[2].x() * v[1].y()) / (v[0].x() * (v[1].y() - v[2].y()) + (v[2].x() - v[1].x()) * v[0].y() + v[1].x() * v[2].y() - v[2].x() * v[1].y());
	float c2 = (x * (v[2].y() - v[0].y()) + (v[0].x() - v[2].x()) * y + v[2].x() * v[0].y() - v[0].x() * v[2].y()) / (v[1].x() * (v[2].y() - v[0].y()) + (v[0].x() - v[2].x()) * v[1].y() + v[2].x() * v[0].y() - v[0].x() * v[2].y());
	float c3 = (x * (v[0].y() - v[1].y()) + (v[1].x() - v[0].x()) * y + v[0].x() * v[1].y() - v[1].x() * v[0].y()) / (v[2].x() * (v[0].y() - v[1].y()) + (v[1].x() - v[0].x()) * v[2].y() + v[0].x() * v[1].y() - v[1].x() * v[0].y());
	return Eigen::Vector3f(c1, c2, c3);
}

Eigen::Matrix4f GRender::GetViewPortMatrix()
{
	Eigen::Matrix4f viewportMatrix;
	viewportMatrix << 0.5 * m_nWidth, 0, 0, 0.5 * m_nWidth,
		0, 0.5 * m_nHeight, 0, 0.5 * m_nHeight,
		0, 0, 1, 0,
		0, 0, 0, 1;
	return viewportMatrix;
}

Bounds2d GRender::CaclBounds(Vector4f pArray[], int nNums)
{
	Bounds2d bounds;
	for (int i = 0; i < nNums; ++i)
	{
		Vector4f vec4Data = pArray[i];
		bounds.nMinX = MIN(bounds.nMinX, vec4Data.x());
		bounds.nMaxX = MAX(bounds.nMaxX, vec4Data.x());
		bounds.nMinY = MIN(bounds.nMinY, vec4Data.y());
		bounds.nMaxY = MAX(bounds.nMaxY, vec4Data.y());
	}
	
	bounds.nMinX = MAX(0, bounds.nMinX);
	bounds.nMaxX = MIN(m_nWidth - 1, bounds.nMaxX);
	bounds.nMinY = MAX(0, bounds.nMinY);
	bounds.nMaxY = MIN(m_nHeight - 1, bounds.nMaxY);

	return bounds;
}

Eigen::Vector4f GRender::Interpolate(float alpha, float beta, float gamma, const Eigen::Vector4f vert1, const Eigen::Vector4f vert2, const Eigen::Vector4f vert3, float weight)
{
	return (alpha * vert1 + beta * vert2 + gamma * vert3) * weight;
}

void GRender::OnMouseDrag(int nDeltaX, int nDeltaY)
{
	m_Camera.Rotate(Eigen::Vector3f(0, 1, 0), nDeltaX / 10.0f);
	m_Camera.Rotate(Eigen::Vector3f(1, 0, 0), -nDeltaY / 10.0f);
}

void GRender::OnKeyDown(int nKey)
{
	switch (nKey)
	{
	case 'W':
		m_Camera.Move(0.5, 0);
		break;
	case 'S':
		m_Camera.Move(-0.5, 0);
		break;
	case 'A':
		m_Camera.Move(0, 0.5);
		break;
	case 'D':
		m_Camera.Move(0, -0.5);
		break;
	default:
		break;
	}
}